home *** CD-ROM | disk | FTP | other *** search
- // Copyright (C) 1996, 1997 Meta Four Software. All rights reserved.
- //
- // Main CatFish application sample code
- //
- //! rev="$Id: catfish.cpp,v 1.5 1997/05/27 00:06:05 jcw Rel $"
-
- #include "stdafx.h"
- #include "catfish.h"
- #include "setupdlg.h"
-
- #include <dos.h> // _dos_findfirst in GetCatalogDate
-
- #ifdef _DEBUG
- #undef THIS_FILE
- static char BASED_CODE THIS_FILE[] = __FILE__;
- #endif
-
- #pragma warning(disable: 4702) // MSVC 1.52 gets confused: unreachable code
-
- /////////////////////////////////////////////////////////////////////////////
- // MSDN Q100770: Using Accelerator Keys When Modal Dialog Box Main Window
-
- HWND ghDlg; // Handle to main dialog box
- HACCEL ghAccelTable; // Handle to accelerator table
-
- CTheApp ThisApp;
-
- /////////////////////////////////////////////////////////////////////////////
- // Use a simple version of localized date, time, and number formating.
-
- static CString sShortDate = "MM/dd/yy"; // "d.M.yyyy", etc
- static bool iTime = false; // true if 24h format
- static bool iTLZero = true; // true if hour has 2 digits
- static char sThousand = ','; // thousands separator
- static char sTime = ':'; // time separator
-
- static void SetInternationalSettings()
- {
- iTime = GetProfileInt("intl", "iTime", 0) != 0;
- iTLZero = GetProfileInt("intl", "iTLZero", 1) != 0;
-
- char buf [30];
-
- if (GetProfileString("intl", "sShortDate", "MM/dd/yy", buf, sizeof buf))
- sShortDate = buf;
-
- if (GetProfileString("intl", "sThousand", ",", buf, sizeof buf))
- sThousand = *buf;
-
- if (GetProfileString("intl", "sTime", ":", buf, sizeof buf))
- sTime = *buf;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // Convert a number to comma-separated format, grouped in units of three.
- // Optionally prefix with spaces (assuming two spaces is width of one digit).
- // Finally, the zero value can be changed to a '-' upon request.
- //
- // Note: In many places, the code is simplified by the assumption that
- // every digit has exactly the same width as two space characters.
- // This works for the selected font (MS Sans Serif, font size 8).
- // It allows us to present a nice columnar interface without having
- // to figure out each of the string position in pixels. There are
- // several more assumptions like this (e.g. "k " is like "Mb").
-
- static CString CommaNum(DWORD num, int groups =0, BOOL zero =TRUE)
- {
- CString s;
- s.Format("%lu", num);
-
- int g = 0;
- int n = s.GetLength();
- while (n > 3)
- {
- n -= 3;
- s = s.Left(n) + sThousand + s.Mid(n);
- ++g;
- }
-
- if (--groups >= 0)
- {
- int w = ((3 - n) % 3) * 2;
- if (g < groups)
- w += 7 * (groups - g);
-
- s = CString (' ', w) + s;
- }
-
- if (!zero && (s == "0" || s.Right(2) == " 0"))
- s = s.Left(s.GetLength() - 1) + " -";
-
- return s;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // Convert a DOS date and TIME words to short format strings.
- // Lets be nice to a lot of people and adopt their local conventions.
-
- static CString ShortDate(WORD date)
- {
- if (date == 0)
- return ""; // will be ok as long as the date is last item
-
- int w = 0;
-
- char buf [10];
- char* q = buf;
-
- // decode the short date, deal with 1- and 2-digit fields
- const char* p = sShortDate;
- while (*p)
- {
- int i;
-
- switch (*p++)
- {
- default: *q++ = *(p-1);
- continue;
-
- case 'd': i = date & 0x1F;
- break;
-
- case 'M': i = (date >> 5) & 0x0F;
- break;
-
- case 'y': i = ((date >> 9) + 80) % 100;
- break; // 4-digit years are treated as 2-digit
-
- }
-
- if (i < 10 && *p != *(p-1))
- ++w;
- else
- *q++ = (char) (i / 10 + '0');
-
- *q++ = (char) (i % 10 + '0');
-
- while (*p == *(p-1))
- ++p;
- }
-
- // centering is easy, since one digit is as wide as two spaces
- CString t (' ', 2 * w);
- // alignment depends on whether the year is first or last
- if (sShortDate[0] == 'y')
- return CString (buf, q - buf) + t;
-
- return t + CString (buf, q - buf);
- }
-
- static CString ShortTime(WORD time)
- {
- int h = time >> 11;
- int m = (time >> 5) & 0x3F;
- char ampm = "ap" [h / 12];
-
- if (!iTime)
- h = (h + 11) % 12 + 1; // dec, then inc, so 0 becomes 12
-
- CString s;
- s.Format("%02d%c%02d", h, sTime, m);
-
- if (!iTime)
- s += ampm;
-
- if (!iTLZero && s[0] == '0')
- s = " " + s.Mid(1); // replace leading zero with two spaces
-
- return s;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // Make a string fit in the specified number of pixels on given device.
- // Characters at the end are replaced by an ellipsis to make the string fit.
- // There is some trickery in here to optimize this very common calculation.
-
- static BOOL FitString(CDC* dc, CString& text, int width)
- {
- CSize sz = dc->GetTextExtent(text, text.GetLength());
- if (sz.cx <= width)
- return TRUE; // make the most common case fast
-
- // Assumption: "...xyz" is just as wide as "xyz..."
- CString s = "..." + text;
-
- int n = s.GetLength();
- while (--n > 3)
- {
- sz = dc->GetTextExtent(text, n);
- if (sz.cx <= width)
- break;
- }
-
- text = text.Left(n - 3) + "...";
- return FALSE;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // Disables redraw and clears listbox, will reset normal state in destructor
-
- class ListBoxFreezer
- {
- public:
- ListBoxFreezer (CListBox& lb)
- : list (lb)
- {
- list.SetRedraw(FALSE);
- list.ResetContent();
- }
-
- ~ListBoxFreezer ()
- {
- list.SetRedraw(TRUE);
- list.Invalidate();
- }
-
- private:
- CListBox& list;
- };
-
- /////////////////////////////////////////////////////////////////////////////
- // Return file date in display format, or an empty string if file not present
-
- CString GetCatalogDate(CString& catName)
- {
- CString s = catName;
- s += FILE_TYPE;
-
- #ifndef _WIN32
- _find_t fbuf;
- if (_dos_findfirst(s, _A_NORMAL, &fbuf) != 0)
- return "";
-
- // pick up the name as it is stored on disk (properly capitalized)
- s = fbuf.name;
- ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
- catName = s.Left(s.GetLength() - 4);
-
- return ShortDate((WORD) fbuf.wr_date) + " "
- + ShortTime((WORD) fbuf.wr_time);
- #endif
- return "?";
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // The one and only application object
-
- CTheApp::CTheApp ()
- : CWinApp ("CatFish")
- {
- }
-
- BOOL CTheApp::InitInstance()
- {
- SetDialogBkColor();
- SetInternationalSettings();
-
- ghAccelTable = LoadAccelerators(AfxGetInstanceHandle(),
- MAKEINTRESOURCE(IDD_MAIN_DIALOG));
-
- // the following is required to let a dialog box have an icon
- static WNDCLASS wndclass;
- if (!wndclass.lpfnWndProc)
- {
- wndclass.lpfnWndProc = DefDlgProc;
- wndclass.cbWndExtra = DLGWINDOWEXTRA ;
- wndclass.hInstance = m_hInstance;
- wndclass.hIcon = LoadIcon(AFX_IDI_STD_FRAME);
- wndclass.lpszClassName = "CATFISHCLASS";
-
- RegisterClass(&wndclass);
- }
-
- // enter a modal loop right now
- CMainDlgWindow mainDlg;
- m_pMainWnd = &mainDlg;
-
- mainDlg.Execute(stricmp(m_lpCmdLine, "/f") == 0);
-
- // and then return false to skip the main application run loop
- return FALSE;
- }
-
- BOOL CTheApp::ProcessMessageFilter(int code, LPMSG lpMsg)
- {
- if (code < 0)
- CWinApp::ProcessMessageFilter(code, lpMsg);
-
- if (ghDlg && ghAccelTable)
- {
- if (::TranslateAccelerator(ghDlg, ghAccelTable, lpMsg))
- return(TRUE);
- }
-
- return CWinApp::ProcessMessageFilter(code, lpMsg);
- }
-
- /////////////////////////////////////////////////////////////////////////////
-
- BEGIN_MESSAGE_MAP(CMainDlgWindow, CDialog)
- //{{AFX_MSG_MAP(CMainDlgWindow)
- ON_WM_CLOSE()
- ON_WM_DRAWITEM()
- ON_LBN_SELCHANGE(IDC_CAT_LIST, OnSelchangeCatList)
- ON_LBN_SELCHANGE(IDC_TREE_LIST, OnSelchangeTreeList)
- ON_LBN_DBLCLK(IDC_TREE_LIST, OnDblclkTreeList)
- ON_LBN_SELCHANGE(IDC_FILE_LIST, OnSelchangeFileList)
- ON_BN_CLICKED(IDC_FIND_BTN, OnFindBtn)
- ON_BN_CLICKED(IDC_SETUP_BTN, OnSetupBtn)
- ON_LBN_DBLCLK(IDC_FILE_LIST, OnDblclkFileList)
- ON_COMMAND(ID_FIND_NEXT, OnFindNext)
- ON_COMMAND(ID_FIND_PREV, OnFindPrev)
- ON_COMMAND(ID_SORT_BY_NAME, OnSortByName)
- ON_COMMAND(ID_SORT_BY_SIZE, OnSortBySize)
- ON_COMMAND(ID_SORT_BY_DATE, OnSortByDate)
- ON_COMMAND(ID_SORT_REVERSE, OnSortReverse)
- ON_WM_DESTROY()
- ON_WM_LBUTTONDOWN()
- ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
- ON_COMMAND(ID_FILE_EXPORT, OnFileExport)
- ON_COMMAND(ID_FILE_REFRESH, OnFileRefresh)
- ON_WM_CHAR()
- ON_COMMAND(ID_FIND_CMD, OnFindBtn)
- ON_COMMAND(ID_FILE_SETUP, OnSetupBtn)
- ON_LBN_DBLCLK(IDC_CAT_LIST, OnSetupBtn)
- ON_COMMAND(ID_EDIT_FIND, OnFindBtn)
- //}}AFX_MSG_MAP
- ON_COMMAND(ID_FILE_EXIT, OnClose)
- ON_COMMAND(ID_HELP, OnHelp)
- END_MESSAGE_MAP()
-
- /////////////////////////////////////////////////////////////////////////////
-
- CMainDlgWindow::CMainDlgWindow ()
- : CDialog (IDD_MAIN_DIALOG),
- m_storage (0), m_fileDir (-1), m_treeDir (-1), m_dc (0),
- m_sortProp (&pName), m_sortReverse (false), m_startFind (false)
- {
- //{{AFX_DATA_INIT(CMainDlgWindow)
- //}}AFX_DATA_INIT
- }
-
- CMainDlgWindow::~CMainDlgWindow ()
- {
- delete m_storage;
- }
-
- int CMainDlgWindow::Execute(bool find)
- {
- m_startFind = find;
-
- return DoModal();
- }
-
- void CMainDlgWindow::DoDataExchange(CDataExchange* pDX)
- {
- CDialog::DoDataExchange(pDX);
- //{{AFX_DATA_MAP(CMainDlgWindow)
- DDX_Control(pDX, IDC_FIND_BTN, m_findBtn);
- DDX_Control(pDX, IDC_PATH_FRAME, m_pathFrame);
- DDX_Control(pDX, IDC_TREE_LIST, m_treeList);
- DDX_Control(pDX, IDC_FILE_LIST, m_fileList);
- DDX_Control(pDX, IDC_CAT_LIST, m_catList);
- DDX_Control(pDX, IDC_MSG_TEXT, m_msgText);
- DDX_Control(pDX, IDC_INFO_TEXT, m_infoText);
- DDX_Control(pDX, IDC_TREE_PATH, m_treePath);
- //}}AFX_DATA_MAP
- }
-
- void CMainDlgWindow::OnCancel()
- {
- ::MessageBeep(0); // don't go away on ESC key
- }
-
- void CMainDlgWindow::OnClose()
- {
- EndDialog(IDOK);
- }
-
- void CMainDlgWindow::OnDestroy()
- {
- CDialog::OnDestroy();
-
- SetCatalog("");
- }
-
- BOOL CMainDlgWindow::OnInitDialog()
- {
- CDialog::OnInitDialog();
-
- ghDlg = m_hWnd;
-
- // create a small font for several of the dialog box items
- LOGFONT lf;
- memset(&lf, 0, sizeof(LOGFONT));
- lf.lfHeight = -8;
- strcpy(lf.lfFaceName, "MS Sans Serif");
- m_font.CreateFontIndirect(&lf);
-
- m_msgText.SetFont(&m_font, FALSE);
- m_infoText.SetFont(&m_font, FALSE);
- m_catList.SetFont(&m_font, FALSE);
- m_treeList.SetFont(&m_font, FALSE);
- m_fileList.SetFont(&m_font, FALSE);
-
- // determine the character height and set owner-draw lists accordingly
- {
- CClientDC dc (this);
- CFont* oldFont = dc.SelectObject(&m_font);
-
- TEXTMETRIC tm;
- VERIFY(dc.GetTextMetrics(&tm));
-
- dc.SelectObject(oldFont);
-
- m_catList.SetItemHeight(0, tm.tmHeight);
- m_treeList.SetItemHeight(0, tm.tmHeight);
- m_fileList.SetItemHeight(0, tm.tmHeight);
- }
-
- // fill the list of catalogs
- m_catList.Dir(0, "*" FILE_TYPE);
-
- // default file sort order is by filename
- SortFileList(pName);
-
- // show contents now, before potential slow catalog loading starts
- ShowWindow(ThisApp.m_nCmdShow);
- UpdateWindow();
-
- m_catList.SetCurSel(0);
- OnSelchangeCatList();
-
- m_infoText.SetWindowText("http://purl.net/meta4/metakit");
-
- if (m_catList.GetCount() == 0)
- OnHelp();
-
- if (m_startFind)
- OnFindBtn();
-
- return TRUE; // return TRUE unless you set the focus to a control
- }
-
- // notification handler for owner-draw listboxes
- void CMainDlgWindow::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
- {
- int n = (int) lpDrawItemStruct->itemID;
- if (n == -1)
- return;
-
- m_item = lpDrawItemStruct;
- m_dc = CDC::FromHandle(m_item->hDC);
-
- if (m_item->itemAction == ODA_FOCUS)
- {
- m_dc->DrawFocusRect(&m_item->rcItem);
- return;
- }
-
- if (m_item->itemState & ODS_SELECTED)
- {
- m_dc->SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
- m_dc->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
- }
- else
- {
- m_dc->SetBkColor(GetSysColor(COLOR_WINDOW));
- m_dc->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
- }
-
- switch (nIDCtl)
- {
- case IDC_CAT_LIST: OnDrawCatItem(n); break;
- case IDC_TREE_LIST: OnDrawDirItem(n); break;
- case IDC_FILE_LIST: OnDrawFileItem(n); break;
- }
-
- if ((m_item->itemState & ODS_FOCUS) && m_item->itemAction != ODA_SELECT)
- m_dc->DrawFocusRect(&m_item->rcItem);
- }
-
- // common code to draw a text string in a listbox item
- void CMainDlgWindow::DrawItemText(const CString& text, int off)
- {
- RECT& rect = m_item->rcItem;
-
- m_dc->ExtTextOut(rect.left + off + 2, rect.top,
- off ? 0 : ETO_OPAQUE, &rect,
- text, text.GetLength(), 0);
- }
-
- // draw one item in the catalog listbox
- void CMainDlgWindow::OnDrawCatItem(int n)
- {
- CString s;
- m_catList.GetText(n, s);
-
- ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
- s = s.Left(s.GetLength() - 4);
-
- CString date = GetCatalogDate(s);
-
- FitString(m_dc, s, 72);
- DrawItemText(s);
-
- DrawItemText(date, 72);
- }
-
- // draw one item in the tree listbox
- void CMainDlgWindow::OnDrawDirItem(int n)
- {
- int dir = (int) m_treeList.GetItemData(n);
- BOOL up = n == 0 || pParent (m_currCat[dir]) != m_treeDir;
-
- if (up && !(m_item->itemState & ODS_SELECTED))
- m_dc->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
-
- CString s = pName (m_currCat[dir]);
- if (dir == 0)
- s = "(root)";
-
- if (!up)
- s = (m_dirCounts[dir] ? "+ " : " ") + s;
-
- FitString(m_dc, s, 78);
- DrawItemText(s);
-
- DWORD t = m_kiloTotals[dir];
- if (t <= 999999)
- s = CommaNum(t, 2) + " k ";
- else
- s = CommaNum((t + 999) / 1000, 2) + " Mb";
-
- s += CommaNum(m_dirCounts[dir], 2, FALSE).Mid(2) + " "
- + CommaNum(m_fileCounts[dir], 2, FALSE) + " "
- + ShortDate(m_lastDates[dir]);
-
- DrawItemText(s, 78);
- }
-
- // draw one item in the file listbox
- void CMainDlgWindow::OnDrawFileItem(int n)
- {
- c4_RowRef file = m_fileSort[n];
-
- CString s = pName (file);
- FitString(m_dc, s, 85);
- DrawItemText(s);
-
- s = CommaNum(pSize (file), 3) + " " + ShortDate((WORD) pDate (file));
- DrawItemText(s, 85);
- }
-
- // pressing F1 leads to an brief help screen
- void CMainDlgWindow::OnHelp()
- {
- CDialog dlg (IDD_WELCOME_DLG);
- dlg.DoModal();
- }
-
- // there is of course also an about box
- void CMainDlgWindow::OnAppAbout()
- {
- CDialog dlg (IDD_ABOUTBOX);
- dlg.DoModal();
- }
-
- // find file entries
- void CMainDlgWindow::OnFindBtn()
- {
- int n = m_catList.GetCurSel();
- ASSERT(n != LB_ERR);
-
- CString s;
- m_catList.GetText(n, s);
-
- ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
- s = s.Left(s.GetLength() - 4);
-
- s.MakeUpper();
-
- if (m_findDlg.Execute(s))
- OnFindNext();
- }
-
- // setup catalogs
- void CMainDlgWindow::OnSetupBtn()
- {
- DoSetup(false);
- }
-
- // update catalog
- void CMainDlgWindow::OnFileRefresh()
- {
- int n = m_catList.GetCurSel();
- if (n == LB_ERR)
- return; // the easy way out, better would be to disable the menu item
-
- DoSetup(true);
- }
-
- // setup catalogs
- void CMainDlgWindow::DoSetup(bool now)
- {
- CSetupDialog dlg;
-
- int n = m_catList.GetCurSel();
- if (n != LB_ERR)
- {
- CString s;
- m_catList.GetText(n, s);
-
- ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
- dlg.m_name = s.Left(s.GetLength() - 4);
- }
-
- SetCatalog(""); // make sure no catalog is in use during setup
-
- dlg.Execute(now);
-
- {
- ListBoxFreezer frozen (m_catList);
-
- m_catList.Dir(0, "*" FILE_TYPE);
-
- // attempt to maintain the current selection
- if (m_catList.SelectString(-1, dlg.m_name) == LB_ERR)
- m_catList.SetCurSel(0);
- }
-
- OnSelchangeCatList();
- }
-
- // adjust the title to show which catalog is selected
- void CMainDlgWindow::ConstructTitle()
- {
- CString s = m_currCatName;
- ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
- s = s.Left(s.GetLength() - 4);
-
- GetCatalogDate(s); // for side-effect: proper file name capitalization
-
- CString root = pName (m_currCat[0]);
- if (!root.IsEmpty())
- s += " - " + root;
-
- s = "CatFish - " + s;
-
- CString title;
- GetWindowText(title);
-
- if (title != s)
- SetWindowText(s);
- }
-
- // select a catalog and update the dialog contents
- void CMainDlgWindow::SetCatalog(const char* catName)
- {
- if (m_currCatName == catName)
- return; // don't bother, the catalog is currently loaded
-
- SetTreeDir(-1);
-
- // An important side effect is that m_fileView is cleared before the
- // storage class is destroyed. Otherwise, the entire view would be
- // loaded into memory since the underlying file is about to go away.
- SetFileDir(-1);
-
- m_currCat = c4_View (); // see comment about m_fileView
- delete m_storage;
- m_storage = 0;
-
- m_dirCounts.RemoveAll();
- m_fileCounts.RemoveAll();
- m_lastDates.RemoveAll();
- m_kiloTotals.RemoveAll();
-
- m_currCatName = catName;
- if (m_currCatName.IsEmpty())
- return;
-
- // loading and calculations may take some time
- HCURSOR oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
-
- m_storage = DEBUG_NEW c4_Storage (m_currCatName, false);
- m_currCat = m_storage->View("dirs");
-
- ConstructTitle();
-
- int n = m_currCat.GetSize();
- m_dirCounts.InsertAt(0, 0, n);
- m_fileCounts.InsertAt(0, 0, n);
- m_lastDates.InsertAt(0, 0, n);
- m_kiloTotals.InsertAt(0, 0, n);
-
- // this loop calculates all cumulative totals and dates,
- // mathematicians call this the "transitive closure" ...
- while (--n >= 0)
- {
- c4_RowRef dir = m_currCat[n];
-
- int date = 0;
- DWORD total = 0;
-
- c4_View files = pFiles (dir);
-
- for (int i = 0; i < files.GetSize(); ++i)
- {
- c4_RowRef file = files[i];
-
- total += pSize (file);
- if (date < pDate (file))
- date = (int) pDate (file);
- }
-
- ASSERT(i == files.GetSize());
- m_fileCounts[n] += (WORD) i;
- m_kiloTotals[n] += (total + 1023) / 1024;
-
- if (m_lastDates[n] < (WORD) date)
- m_lastDates[n] = (WORD) date;
-
- int parDir = pParent (dir);
- if (parDir != n)
- {
- m_dirCounts[parDir] += m_dirCounts[n] + 1;
- m_fileCounts[parDir] += m_fileCounts[n];
- m_kiloTotals[parDir] += m_kiloTotals[n];
-
- if (m_lastDates[parDir] < m_lastDates[n])
- m_lastDates[parDir] = m_lastDates[n];
- }
- }
-
- SetCursor(oldCursor);
-
- if (m_currCat.GetSize() > 0)
- SetTreeDir(0);
- }
-
- // select a directory in the tree and update the dialog contents
- void CMainDlgWindow::SetTreeDir(int dirNum)
- {
- if (dirNum != m_treeDir)
- {
- m_treeDir = dirNum;
-
- ListBoxFreezer frozen (m_treeList);
-
- if (dirNum >= 0)
- {
- // select the appropriate subdirectories and sort them by name
- c4_View selsort = m_currCat.Select(pParent [dirNum]).SortOn(pName);
-
- for (int j = 0; j < selsort.GetSize(); ++j)
- {
- // map each entry back to the m_currCat view
- int ix = m_currCat.GetIndexOf(selsort[j]);
- ASSERT(ix >= 0);
-
- // don't add the root entry, it doesn't sort correctly
- if (ix > 0)
- {
- int k = m_treeList.AddString("");
- m_treeList.SetItemData(k, ix);
- }
- }
-
- // insert the parent directories in reverse order in front
- for (;;)
- {
- m_treeList.InsertString(0, "");
- m_treeList.SetItemData(0, dirNum);
-
- if (dirNum == m_treeDir)
- m_treeList.SetCurSel(0);
-
- if (dirNum <= 0)
- break;
-
- dirNum = (int) pParent (m_currCat[dirNum]);
- }
-
- // make sure the focus item is the same as the selection
- // InsertString moves the selection but not the focus...
- m_treeList.SetCurSel(m_treeList.GetCurSel());
- }
- }
-
- SetFileDir(m_treeDir);
- }
-
- // select a list of files and update the dialog contents
- void CMainDlgWindow::SetFileDir(int dirNum)
- {
- if (dirNum != m_fileDir)
- {
- m_fileDir = dirNum;
-
- ListBoxFreezer frozen (m_fileList);
-
- if (dirNum >= 0)
- {
- m_fileView = pFiles (m_currCat[dirNum]);
-
- CString root = fFullPath(m_currCat, 0);
- CString path = fFullPath(m_currCat, dirNum);
-
- // remove common root prefix
- path = path.Mid(root.GetLength());
- if (path.IsEmpty())
- path = "(root)";
-
- // fit the path, prefixing "..." to the head if necessary
- CDC* dc = m_treePath.GetDC();
- if (dc)
- {
- CRect r;
- m_treePath.GetClientRect(&r);
-
- CString s = path;
- // loop uses FitString to measure, but not its result
- while (!FitString(dc, path, r.right))
- {
- int n = s.SpanExcluding("\\/:").GetLength() + 1;
- if (n > 10)
- n = 10; // if the last segment is huge, chop it off
-
- path = "..." + s.Mid(n-1);
- s = s.Mid(n);
- }
-
- m_treePath.ReleaseDC(dc);
- }
-
- m_treePath.SetWindowText(path);
-
- for (int i = 0; i < m_fileView.GetSize(); ++i)
- m_fileList.AddString("");
- }
- else
- {
- m_fileSort = c4_View ();
- m_fileView = c4_View ();
- m_treePath.SetWindowText("");
- }
-
- // this sets up the appropriate m_fileSort view
- SortFileList(*m_sortProp);
- }
-
- // always reset the file list selection
- m_fileList.SetCurSel(-1);
-
- OnSelchangeFileList();
- }
-
- // the catalog selection changed
- void CMainDlgWindow::OnSelchangeCatList()
- {
- CString s;
-
- int n = m_catList.GetCurSel();
- if (n != LB_ERR)
- m_catList.GetText(n, s);
-
- SetCatalog(s);
- }
-
- // the directory selection changed
- void CMainDlgWindow::OnSelchangeTreeList()
- {
- int n = m_treeList.GetCurSel();
-
- m_findBtn.EnableWindow(n >= 0);
-
- if (n >= 0)
- n = (int) m_treeList.GetItemData(n);
-
- SetFileDir(n);
- }
-
- // descend into an entry in the directory tree
- void CMainDlgWindow::OnDblclkTreeList()
- {
- int n = m_treeList.GetCurSel();
- if (n >= 0)
- {
- n = (int) m_treeList.GetItemData(n);
-
- // don't allow descending into a dir with no subdirs
- if (m_dirCounts[n] == 0)
- {
- MessageBeep(0);
- return;
- }
- }
-
- SetTreeDir(n);
- }
-
- // the file selection changed
- void CMainDlgWindow::OnSelchangeFileList()
- {
- CString s;
-
- int n = m_fileList.GetCurSel();
- if (n >= 0)
- {
- c4_RowRef file = m_fileSort[n];
- s = pName (file);
- }
- else if (m_fileDir >= 0)
- s.Format("%d files", m_fileSort.GetSize());
-
- m_infoText.SetWindowText(s);
- }
-
- void CMainDlgWindow::OnDblclkFileList()
- {
- int n = m_fileList.GetCurSel();
- if (n >= 0)
- {
- c4_RowRef file = m_fileSort[n];
- CString s = pName (file);
-
- CString path = fFullPath(m_currCat, m_fileDir); // also the working dir
-
- if ((UINT) ShellExecute(m_hWnd, 0, path + s, 0, path, SW_SHOWNORMAL) >= 32)
- return; // selected file succesfully launched
- }
-
- MessageBeep(0);
- }
-
- // Adjust specified menu entry and label text to indicate current sort order
- void CMainDlgWindow::AdjustDisplay(CCmdUI& cui, int ix_, c4_Property& prop_,
- int label_, const char* text_)
- {
- bool match = m_sortProp == &prop_;
-
- cui.m_nIndex = ix_;
- cui.SetRadio(match);
-
- // include "+" or "-" in the label corresponding to the current sort field
- CString s = text_;
- if (match)
- s += m_sortReverse ? " (-)" : " (+)";
-
- CWnd* wnd = GetDlgItem(label_);
- ASSERT(wnd);
-
- wnd->SetWindowText(s);
- }
-
- // Sort the file list and adjust menu items and label texts
- void CMainDlgWindow::SortFileList(c4_Property& prop_, bool toggle_)
- {
- if (m_sortProp != &prop_)
- {
- m_sortProp = &prop_;
- m_sortReverse = false;
- }
- else if (toggle_)
- m_sortReverse = !m_sortReverse;
-
- // update all menu check marks here, since CCmdUI doesn't work in dialogs
- CMenu* menu = GetMenu();
- ASSERT(menu);
-
- menu = menu->GetSubMenu(0); // the "File" menu
- ASSERT(menu);
-
- CMenu* sub = menu->GetSubMenu(3); // the "Sort Files" entry (ouch!)
- ASSERT(sub);
-
- // use CCmdUI, not CheckMenuItem, because it can set nice bullet marks
- CCmdUI cui;
- cui.m_pMenu = sub;
- cui.m_nIndexMax = sub->GetMenuItemCount();
- ASSERT(cui.m_nIndexMax == 5); // name, size, date, <sep>, reverse
-
- AdjustDisplay(cui, 0, pName, IDC_NAME_LABEL, "File &name");
- AdjustDisplay(cui, 1, pSize, IDC_SIZE_LABEL, "Size");
- AdjustDisplay(cui, 2, pDate, IDC_DATE_LABEL, "Date");
-
- // the "Reverse" menu item uses a regular check mark
- cui.m_nIndex = 4;
- cui.SetCheck(m_sortReverse);
-
- // sorting may take some time
- HCURSOR oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
-
- // figure out the index of the row that was selected, if any
- int n = m_fileList.GetCurSel();
- if (n >= 0)
- {
- n = m_fileView.GetIndexOf(m_fileSort [n]);
- ASSERT(n >= 0);
- }
-
- // define the sort order and make sure the list is redrawn
- if (m_sortReverse)
- m_fileSort = m_fileView.SortOnReverse(prop_, prop_);
- else
- m_fileSort = m_fileView.SortOn(prop_);
-
- m_fileList.Invalidate();
-
- // restore the selection to the original item
- if (n >= 0)
- {
- int m = m_fileSort.Find(m_fileView [n]); // where is that row now?
- ASSERT(m >= 0);
-
- m_fileList.SetCurSel(m);
- }
-
- SetCursor(oldCursor);
- }
-
- void CMainDlgWindow::OnSortByName()
- {
- SortFileList(pName);
- }
-
- void CMainDlgWindow::OnSortBySize()
- {
- SortFileList(pSize);
- }
-
- void CMainDlgWindow::OnSortByDate()
- {
- SortFileList(pDate);
- }
-
- void CMainDlgWindow::OnSortReverse()
- {
- SortFileList(*m_sortProp, true);
- }
-
- void CMainDlgWindow::OnLButtonDown(UINT nFlags, CPoint point)
- {
- // catch mouse clicks on the header texts to alter the file sort order
- CWnd* wnd = ChildWindowFromPoint(point);
- if (wnd)
- switch (wnd->GetDlgCtrlID())
- {
- case IDC_NAME_LABEL: SortFileList(pName, true); return;
- case IDC_SIZE_LABEL: SortFileList(pSize, true); return;
- case IDC_DATE_LABEL: SortFileList(pDate, true); return;
-
- case IDC_LOGO1: // handle clicks on the fake logo
- case IDC_LOGO2:
- case IDC_LOGO3: OnAppAbout(); return;
- }
-
- CDialog::OnLButtonDown(nFlags, point);
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // The following class maintains most of the state required to iterate
- // over all entries to satisfy a find request. This is a pretty messy
- // approach to be able to use this in both forward and backward modes.
-
- class CFindState
- {
- public:
- CFindState (CMainDlgWindow& dlg_)
- : _dlg (dlg_), findStorage (0)
- {
- // searching may take some time
- oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
- }
-
- ~CFindState ()
- {
- SetCursor(oldCursor);
-
- findCat = c4_View();
- findList = c4_View();
- delete findStorage;
- }
-
- bool Initialize()
- {
- lastCat = _dlg.m_catList.GetCurSel();
- if (lastCat < 0 || _dlg.m_treeDir < 0 || _dlg.m_fileDir < 0)
- {
- MessageBeep(0);
- return false;
- }
-
- findCatName = _dlg.m_currCatName;
- findCat = _dlg.m_currCat;
- findList = _dlg.m_fileSort;
-
- // prepare for iteration
- lastDir = _dlg.m_fileDir;
- lastSel = _dlg.m_fileList.GetCurSel(); // can be -1
-
- return true;
- }
-
- void SetSort(const c4_View& view_)
- {
- ASSERT(_dlg.m_sortProp);
- c4_Property& prop = *_dlg.m_sortProp;
-
- if (_dlg.m_sortReverse)
- findList = view_.SortOnReverse(prop, prop);
- else
- findList = view_.SortOn(prop);
- }
-
- bool IsStartDir(int dir_, int cat_)
- {
- return dir_ == lastDir && cat_ == lastCat;
- }
-
- void Select(int sel_, int dir_)
- {
- // adjust to the found catalog and directory
- _dlg.SetCatalog(findCatName);
- _dlg.SetTreeDir(dir_);
- // then match the selection and update the status fields
- _dlg.m_fileList.SetCurSel(sel_);
- _dlg.OnSelchangeFileList();
-
- _dlg.m_fileList.SetFocus(); // so arrows work as expected
- _dlg.m_fileList.UpdateWindow(); // show before new find can start
- }
-
- void UseCatalog(int cat_)
- {
- // show which catalog we're currently searching
- _dlg.m_catList.SetCurSel(cat_);
-
- findCat = c4_View();
- findList = c4_View();
- delete findStorage;
- findStorage = 0;
-
- _dlg.m_catList.GetText(cat_, findCatName);
- findStorage = DEBUG_NEW c4_Storage (findCatName, false);
- findCat = findStorage->View("dirs");
- }
-
- // check if any key is pressed, this aborts a lengthy find
- bool WantsToQuit() const
- {
- MSG msg;
- // careful, there may still be keyup's in the queue
- if (!::PeekMessage(&msg, NULL, WM_KEYDOWN, WM_KEYDOWN, PM_NOREMOVE))
- return false;
-
- while (::PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))
- ; // flush all key events
-
- return true;
- }
-
- CMainDlgWindow& _dlg;
- int lastCat, lastDir, lastSel;
- c4_Storage* findStorage;
- CString findCatName;
- c4_View findCat, findList;
- HCURSOR oldCursor;
- };
-
- /////////////////////////////////////////////////////////////////////////////
- // Several aspects of the find code below affect search performance:
- //
- // 1. Each catalog is opened without calculating any statistics
- // 2. Only match fields are accessed, for optimal use of on-demand loading
- // 3. Sorting is only performed *after* at least one entry has been found
- //
- // As a result, searching is quite fast (and THAT is an understatement).
-
- void CMainDlgWindow::OnFindNext()
- {
- CFindState state (*this);
- if (!state.Initialize())
- return;
-
- // prepare for iteration
- int cat = state.lastCat;
- int dir = state.lastDir;
- int sel = state.lastSel;
-
- bool mustSort = false; // avoid resorting when a sorted list is available
- bool first = true; // watch out for infinite loop if never found an entry
-
- for (;;) // loop over each catalog
- {
- int dirCount = state.findCat.GetSize();
-
- while (dir < dirCount) // loop over each subdirectory
- {
- c4_View files = pFiles (state.findCat [dir]);
- int selCount = files.GetSize();
-
- // on first entry into dir, first scan for match in unsorted list
- // this *drastically* improves performance if most dirs are a miss
- if (sel < 0 && m_findDlg.NeedsCompare())
- {
- while (++sel < selCount) // loop over each file
- if (m_findDlg.Match(files[sel]))
- {
- sel = -1; // something matches, prepare to search sorted
- break;
- }
-
- // at this point sel is either -1 or selCount
- }
-
- // only sort if we're really going to use this to scan
- if (mustSort && sel < selCount)
- state.SetSort(files);
-
- while (++sel < selCount) // loop over each file
- if (m_findDlg.Match(state.findList [sel]))
- {
- if (!first && sel >= state.lastSel &&
- state.IsStartDir(dir, cat))
- break; // oops, second time around in start dir, fail
-
- state.Select(sel, dir);
- return;
- }
-
- // if we fell through start dir for the second time, then fail
- // this scans for too many entries but works on empty start dir
- if (state.WantsToQuit() || !first && state.IsStartDir(dir, cat))
- {
- // wrapped around, nothing found
- m_catList.SetCurSel(state.lastCat);
- MessageBeep(0);
- return;
- }
-
- first = false;
-
- sel = -1;
- ++dir; // directories are scanned in breadth first order, hmmm...
- mustSort = true;
- }
-
- dir = 0;
-
- if (m_findDlg.m_singleCat)
- continue; // don't switch to another catalog file
-
- if (++cat >= m_catList.GetCount())
- cat = 0;
-
- state.UseCatalog(cat);
- }
- }
-
- void CMainDlgWindow::OnFindPrev()
- {
- CFindState state (*this);
- if (!state.Initialize())
- return;
-
- // prepare for iteration
- int cat = state.lastCat;
- int dir = state.lastDir;
- int sel = state.lastSel;
-
- bool mustSort = false; // avoid resorting when a sorted list is available
- bool first = true; // watch out for infinite loop if never found an entry
-
- for (;;) // loop over each catalog
- {
- if (dir < 0)
- dir = state.findCat.GetSize() - 1;
-
- while (dir >= 0) // loop over each subdirectory
- {
- c4_View files = pFiles (state.findCat [dir]);
- int selCount = files.GetSize();
-
- if (sel < 0)
- sel = selCount;
-
- // on first entry into dir, first scan for match in unsorted list
- // this *drastically* improves performance if most dirs are a miss
- if (sel >= selCount && m_findDlg.NeedsCompare())
- {
- while (--sel >= 0) // loop over each file
- if (m_findDlg.Match(files[sel]))
- {
- sel = selCount; // matches, prepare to search sorted
- break;
- }
-
- // at this point sel is either -1 or selCount
- }
-
- // only sort if we're really going to use this to scan
- if (mustSort && sel >= 0)
- state.SetSort(files);
-
- while (--sel >= 0) // loop over each file
- if (m_findDlg.Match(state.findList[sel]))
- {
- if (!first && sel <= state.lastSel &&
- state.IsStartDir(dir, cat))
- break; // oops, second time around in start dir, fail
-
- state.Select(sel, dir);
- return;
- }
-
- // if we fell through start dir for the second time, then fail
- // this scans for too many entries but works on empty start dir
- if (state.WantsToQuit() || !first && state.IsStartDir(dir, cat))
- {
- // wrapped around, nothing found
- m_catList.SetCurSel(state.lastCat);
- MessageBeep(0);
- return;
- }
-
- first = false;
-
- sel = -1;
- --dir; // directories are scanned in breadth first order, hmmm...
- mustSort = true;
- }
-
- ASSERT(dir == -1);
-
- if (m_findDlg.m_singleCat)
- continue; // don't switch to another catalog file
-
- if (cat == 0)
- cat = m_catList.GetCount();
- --cat;
-
- state.UseCatalog(cat);
- }
- }
-
- void CMainDlgWindow::OnFileExport()
- {
- int n = m_catList.GetCurSel();
- if (n == LB_ERR)
- return; // the easy way out, better would be to disable the menu item
-
- CString s = m_currCatName;
- ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
- s = s.Left(s.GetLength() - 4);
-
- CFileDialog dlg (FALSE, "txt", s + ".txt",
- OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR);
-
- if (dlg.DoModal() == IDOK)
- {
- // exporting may take some time
- HCURSOR oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
-
- CStdioFile output (dlg.GetPathName(), CFile::typeText |
- CFile::modeReadWrite | CFile::modeCreate);
-
- CWordArray pending;
- pending.Add(0);
-
- CString title;
- GetWindowText(title);
-
- output.WriteString(title);
-
- do
- {
- int index = pending[0];
- pending.RemoveAt(0);
-
- output.WriteString("\n\n " + fFullPath(m_currCat, index) + "\n");
-
- c4_View files = pFiles (m_currCat[index]);
- for (int j = 0; j < files.GetSize(); ++j)
- {
- c4_RowRef file = files[j];
- output.WriteString("\n "
- + (" " + CommaNum(pSize (file), 3)).Right(13)
- + " " + ShortDate((WORD) pDate (file))
- + " " + (CString) pName (file));
- }
-
- c4_View children = m_currCat.Select(pParent [index]).SortOn(pName);
-
- int n = children.GetSize();
- if (n > 0)
- {
- pending.InsertAt(0, 0, n);
- for (int i = 0; i < n; ++i)
- pending[i] = (WORD) m_currCat.GetIndexOf(children[i]);
- }
-
- // root has itself as parent, avoid a runaway loop
- if (pending.GetSize() > 0 && pending[0] == 0)
- pending.RemoveAt(0);
-
- } while (pending.GetSize() > 0);
-
- output.WriteString("\n\nDone.\n");
-
- SetCursor(oldCursor);
- }
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // $Id: catfish.cpp,v 1.5 1997/05/27 00:06:05 jcw Rel $
-